﻿﻿using System;
using System.Collections.Generic;
using System.Text;
using AdaptiveAgents.Loggers;
using AdaptiveAgents.Agents;
using AdaptiveAgents.Games;

namespace AdaptiveAgents.Experiments
{
    /// <summary>
    /// Type of experiment
    /// </summary>
    public enum ExperimentType { Normal, BlockTest, UtilityAverage }

    /// <summary>
    /// Abstract experiment. Contains basic methods for running an experiment.
    /// </summary>
    public abstract class Experiment
    {
        //data members
        protected int numGames;
        protected int numRounds;
        protected int NumAgents {get; set;} 
        
        protected double utilitySum = 0;
        protected double utilitySquaredSum = 0;
        protected Logger _logger;
        protected double defaultEpsilon = 0.5;

        Random rand = new Random();

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="numRounds">Number of rounds per game</param>
        /// <param name="numAgents">Number of agents per game</param>
        /// <param name="numGames">Number of games in experiments</param>
        public Experiment(int numRounds,int numAgents,int numGames)
        {
            this.numRounds = numRounds;
            this.NumAgents = numAgents;
            this.numGames = numGames;
        }

        /// <summary>
        /// Gets and Sets default epsilon for all / some agents in the experiment
        /// </summary>
        public double DefaultEpsilon
        {
            get { return defaultEpsilon; }
            set { defaultEpsilon = value; }
        }

        /// <summary>
        /// Gets and Sets the logger of the experiment
        /// </summary>
        public Logger Logger
        {
            get { return _logger; }
            set { _logger = value; }
        }

        /// <summary>
        /// Gets and Sets number of rounds per game
        /// </summary>
        public int NumRounds
        {
            get { return numRounds; }
            set { numRounds = value; }
        }

        /// <summary>
        /// Gets and Sets number of games in experiment
        /// </summary>
        public int NumGames
        {
            get { return numGames; }
            set { numGames = value; }
        }

        /// <summary>
        /// Need to be implemented in deriving class
        /// </summary>
        public abstract void runExperiment();

        /// <summary>
        /// Create a players list according to parameters
        /// </summary>
        /// <param name="numOfAgents">Number of agents per game</param>
        /// <param name="env">EnvironmentType of the game</param>
        /// <param name="epsilon">Default epsilon for all players</param>
        /// <param name="logger">Logger for game documentation</param>
        /// <returns>List of new created players</returns>
        protected List<Agent> generatePlayers(int numOfAgents, Environment env, double epsilon, Logger logger)
        {
            return generatePlayers(numOfAgents, env, 1, epsilon, epsilon, logger);
        }

        /// <summary>
        /// Create a players list according to parameters
        /// </summary>
        /// <param name="numOfAgents">Number of agents per game</param>
        /// <param name="env">EnvironmentType of the game</param>
        /// <param name="numAdaptive">Number of adaptive agents in experiment</param>
        /// <param name="adaptiveEpsilon">Epsilon of the adaptive agents</param>
        /// <param name="normalEpsilon">Epsilon of the regular agents</param>
        /// <param name="logger">Logger for game documentation</param>
        /// <returns>List of new created players</returns>
        protected virtual List<Agent> generatePlayers(int numOfAgents, Environment env, int numAdaptive, double adaptiveEpsilon, double normalEpsilon, Logger logger)
        {
            List<Agent> retList = new List<Agent>(numOfAgents);

            AgentDirector director = new AgentDirector();

            //create adaptive agents
            for (int i = 1; i < numAdaptive; ++i)
            {
                director.Builder = new Agents.NormalBuilder();
                director.construct(0, env, adaptiveEpsilon, logger);
                retList.Add(director.getAgent());
            }

            //create normal agents
            for (int i = retList.Count; i < NumAgents; ++i)
            {
                director.Builder = new Agents.NormalBuilder();
                director.construct(i, env, normalEpsilon, logger);
                retList.Add(director.getAgent());
            }
            return retList;
        }

        /// <summary>
        /// Create a set of 3 agent
        /// </summary>
        /// <param name="env">The environment of the agent</param>
        /// <param name="epsilon0">Epsilon value for agent 0</param>
        /// <param name="epsilon1">Epsilon value for agent 1</param>
        /// <param name="epsilon2">Epsilon value for agent 2</param>
        /// <param name="logger">Logger</param>
        /// <returns>A list of 3 new agents for the game</returns>
        protected List<Agent> generatePlayers(Environment env, double epsilon0, double epsilon1, double epsilon2, Logger logger)
        {
            int numOfAgents = 3;
            List<Agent> retList = new List<Agent>(numOfAgents);

            AgentDirector director = new AgentDirector();

            director.Builder = new Agents.NormalBuilder();
            director.construct(0, env, epsilon0, logger);
            retList.Add(director.getAgent());

            director.Builder = new Agents.NormalBuilder();
            director.construct(1, env, epsilon1, logger);
            retList.Add(director.getAgent());

            director.Builder = new Agents.NormalBuilder();
            director.construct(2, env, epsilon2, logger);
            retList.Add(director.getAgent());

            return retList;
        }

        protected void CheckNumIsBetweenValidRange(ref double comp)
        {
            while (comp == 0 || comp == 1)
            {
                comp = Math.Round(rand.NextDouble(), 1);
            }
        }
    }

}
